#include "Skybox.h"
#include "Texture.h"
#include <glad/glad.h>
#include <GLFW/glfw3.h>

namespace OCPP
{
	Skybox::Skybox()
	{
		float skyboxVertices[] =
		{
			//   Coordinates
			-1.0f, -1.0f,  1.0f,//        7--------6
			 1.0f, -1.0f,  1.0f,//       /|       /|
			 1.0f, -1.0f, -1.0f,//      4--------5 |
			-1.0f, -1.0f, -1.0f,//      | |      | |
			-1.0f,  1.0f,  1.0f,//      | 3------|-2
			 1.0f,  1.0f,  1.0f,//      |/       |/
			 1.0f,  1.0f, -1.0f,//      0--------1
			-1.0f,  1.0f, -1.0f
		};

		unsigned int skyboxIndices[] =
		{

			1, 6, 2,
			6, 1, 5,

			0, 7, 4,
			7, 0, 3,

			4, 6, 5,
			6, 4, 7,

			0, 2, 3,
			2, 0, 1,

			0, 5, 1,
			5, 0, 4,

			3, 6, 7,
			6, 3, 2
		};

		skyboxShader.load("shaders/skybox.vert", "shaders/skybox.frag");

		skyboxShader.Activate();
		skyboxShader.setUniform1i("skybox", 0);

		// Create VAO, VBO, and EBO for the skybox
		unsigned int skyboxVBO, skyboxEBO;
		glGenVertexArrays(1, &skyboxVAO);
		glGenBuffers(1, &skyboxVBO);
		glGenBuffers(1, &skyboxEBO);
		glBindVertexArray(skyboxVAO);
		glBindBuffer(GL_ARRAY_BUFFER, skyboxVBO);
		glBufferData(GL_ARRAY_BUFFER, sizeof(skyboxVertices), &skyboxVertices, GL_STATIC_DRAW);
		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, skyboxEBO);
		glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(skyboxIndices), &skyboxIndices, GL_STATIC_DRAW);
		glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
		glEnableVertexAttribArray(0);
		glBindBuffer(GL_ARRAY_BUFFER, 0);
		glBindVertexArray(0);
		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);

		String facesCubemap[6] =
		{
			"shaders/skybox/right.jpg",
			"shaders/skybox/left.jpg",
			"shaders/skybox/top.jpg",
			"shaders/skybox/bottom.jpg",
			"shaders/skybox/front.jpg",
			"shaders/skybox/back.jpg"
		};

		// Creates the cubemap texture object
		glGenTextures(1, &cubemapTexture);
		glBindTexture(GL_TEXTURE_CUBE_MAP, cubemapTexture);
		glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
		glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
		// These are very important to prevent seams
		glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
		glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
		glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
		// This might help with seams on some systems
		//glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS);

		// Cycles through all the textures and attaches them to the cubemap object
		for (unsigned int i = 0; i < 6; i++)
		{
			Image image;
			image.load(facesCubemap[i]);

			PixelFormat pf = image.getFormat();

            GLenum format = getGLOriginFormat(pf);
            GLenum pixelType = getGLOriginDataType(pf);
            GLenum internalFormat = getGLInternalFormat(pf,false);

			glTexImage2D
			(
				GL_TEXTURE_CUBE_MAP_POSITIVE_X + i,
				0,
				internalFormat,
				image.getWidth(),
				image.getHeight(),
				0,
                format,
                pixelType,
				image.getData()
			);
		}
	}

	Skybox::~Skybox()
	{

	}

	void Skybox::draw(Camera& camera)
	{
		// Since the cubemap will always have a depth of 1.0, we need that equal sign so it doesn't get discarded
		glDepthFunc(GL_LEQUAL);

		skyboxShader.Activate();

		// We make the mat4 into a mat3 and then a mat4 again in order to get rid of the last row and column
		// The last row and column affect the translation of the skybox (which we don't want to affect)
		Matrix4 view = camera.getViewMatrix();
		view.setTrans(Vector3::ZERO);
		Matrix4 projection = camera.getProjectMatrix();
		skyboxShader.setUniformMatrix4fv("view", view);
		skyboxShader.setUniformMatrix4fv("projection", projection);

		glBindVertexArray(skyboxVAO);
		glActiveTexture(GL_TEXTURE0);
		glBindTexture(GL_TEXTURE_CUBE_MAP, cubemapTexture);
		glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_INT, 0);	
		
		// Switch back to the normal depth function
		glDepthFunc(GL_LESS);
	}

}